Skip to content

docs: clarify Service.interface(), VarlinkError, RequestHandler, and server classes#87

Merged
haraldh merged 1 commit intomasterfrom
hh/doc-improvement
Feb 16, 2026
Merged

docs: clarify Service.interface(), VarlinkError, RequestHandler, and server classes#87
haraldh merged 1 commit intomasterfrom
hh/doc-improvement

Conversation

@haraldh
Copy link
Contributor

@haraldh haraldh commented Feb 12, 2026

Improve pydoc documentation to address common points of confusion:

  • Service.interface() expects a varlink interface name that maps to a .varlink file in interface_dir, not a Python class
  • VarlinkError subclasses for user-defined errors require manually constructing the {"error": ..., "parameters": {...}} dict
  • RequestHandler requires a service class variable wired to the Service instance
  • ThreadingServer and ForkingServer live in the varlink package; add docstrings and list all server classes in the module docstring

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Documentation-focused PR to reduce common user confusion around implementing varlink services/servers and raising custom VarlinkErrors.

Changes:

  • Clarifies Service.interface() usage and how .varlink files are located/loaded.
  • Adds guidance on wiring Service into a RequestHandler subclass and running servers.
  • Expands docs for VarlinkError subclasses and adds docstrings for ThreadingServer/ForkingServer; updates package-level server guidance.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
varlink/server.py Enhances pydoc for Service.interface(), RequestHandler, and server classes; adds examples and server-class docstrings.
varlink/error.py Expands VarlinkError docstring with guidance for user-defined errors.
varlink/__init__.py Updates module docstring to explain recommended server setup and enumerates server classes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

specified in the constructor of the Service. It has to end in '.varlink'.
The methods defined on the decorated class directly implement the varlink
interface methods. Each method receives the varlink call parameters as
keyword arguments and must return a dict matching the method's return type.
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says handler methods receive varlink call parameters as keyword arguments, but Service._handle currently invokes methods with positional arguments (in the order of method.in_type.fields) and only uses keyword args for the special _raw/_message/_more/etc parameters. This wording is misleading; update it to describe positional args (and optionally mention the special keyword-only parameters).

Suggested change
keyword arguments and must return a dict matching the method's return type.
positional arguments (in the order of the method's input fields) and must
return a dict matching the method's return type. Additional information may
be passed via special keyword-only parameters (such as ``_raw``, ``_message``,
or ``_more``), if supported by the Service implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +52
>>> class ServiceRequestHandler(varlink.RequestHandler):
>>> service = service
>>>
>>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example mixes unqualified Service(...) with varlink.RequestHandler/varlink.ThreadingServer, but it never shows import varlink (or from varlink import Service). In a typical import varlink usage, Service won’t be in scope, so the snippet is not directly runnable as written. Consider either fully-qualifying the Service construction (varlink.Service(...)) or dropping the varlink. prefixes and keeping everything module-local.

Suggested change
>>> class ServiceRequestHandler(varlink.RequestHandler):
>>> service = service
>>>
>>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler)
>>> class ServiceRequestHandler(RequestHandler):
>>> service = service
>>>
>>> server = ThreadingServer("unix:@example", ServiceRequestHandler)

Copilot uses AI. Check for mistakes.
Comment on lines +293 to +296
:param filename: A varlink interface name (e.g. ``'com.example.service'``).
The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given
to the Service constructor. This must be an interface name or a file path,
**not** a Python class or type.
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service.interface() docstring says filename can be an “interface name or a file path”, but the implementation treats non-absolute values as interface names: it always appends .varlink and prefixes interface_dir. Only absolute paths are opened as-is. Clarify this behavior (and whether callers should pass an absolute path to a .varlink file when using a path).

Suggested change
:param filename: A varlink interface name (e.g. ``'com.example.service'``).
The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given
to the Service constructor. This must be an interface name or a file path,
**not** a Python class or type.
:param filename: Either (1) a varlink interface name (e.g.
``'com.example.service'``), in which case the file
``{filename}.varlink`` is loaded from the ``interface_dir`` given to the
Service constructor, or (2) an absolute path to a ``.varlink`` file,
which is opened directly. Any non-absolute value is treated as an
interface name (with ``.varlink`` appended and ``interface_dir``
prepended), so callers who want to load by path must pass an absolute
path. This must be an interface name or a file path, **not** a Python
class or type.

Copilot uses AI. Check for mistakes.
``@service.interface()`` decorator, wire it to a :class:`varlink.RequestHandler` subclass, and run it
with one of the server classes:

- :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docstring describes varlink.Server as a “single-connection base server”, but Server (like socketserver.TCPServer) can accept multiple connections sequentially; it’s single-threaded / non-concurrent rather than single-connection. Rewording would avoid implying it only handles one connection total.

Suggested change
- :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``)
- :class:`varlink.Server` -- single-threaded base server for sequential connections (analogous to ``socketserver.TCPServer``)

Copilot uses AI. Check for mistakes.
Comment on lines 594 to 595
Import from the ``varlink`` package (``varlink.ForkingServer``), **not** from
``socketserver``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I find this and similar sentences in the patch unclear. I think it's trying to say that other components of varlink library expect this class and not the somewhat similarly named class in socketserver correct? I think it could be worded better in that case, especially since there's no ForkingServer in socketserver (the closest are ForkingTCPServer and other ForkingServer classes - AFAICT).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's me, but this part doesn't look changed. I also don't feel strongly about it and was just providing feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! Thanks

Copy link
Collaborator

@behrmann behrmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice docs improvement, just two minor comments.


class VarlinkError(Exception):
"""The base class for varlink error exceptions"""
"""The base class for varlink error exceptions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small nit: I think in the Python world it's clear that errors are modelled as exceptions, so this is a bit doubled

Suggested change
"""The base class for varlink error exceptions.
"""The base class for varlink errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +29 to +36
# Given: error ActionFailed (field: string) in the .varlink file

class ActionFailed(VarlinkError):
def __init__(self, reason):
VarlinkError.__init__(self, {
"error": "com.example.ActionFailed",
"parameters": {"field": reason},
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can spell this out more clearly:

Suggested change
# Given: error ActionFailed (field: string) in the .varlink file
class ActionFailed(VarlinkError):
def __init__(self, reason):
VarlinkError.__init__(self, {
"error": "com.example.ActionFailed",
"parameters": {"field": reason},
})
# Given: error ActionFailed (field: string) in an org.example.myinterface.varlink file
class ActionFailed(VarlinkError):
def __init__(self, reason):
VarlinkError.__init__(self, {
"error": "org.example.myinterface.ActionFailed",
"parameters": {"field": reason},
})

Also changing com.example to org.example, because it's the more common combination, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

def interface(self, filename):
"""Decorator that registers a class as the handler for a varlink interface.

:param filename: A varlink interface name (e.g. ``'com.example.service'``).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go here and below with org as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

…server classes

Improve pydoc documentation to address common points of confusion:
- Service.interface() expects a varlink interface name that maps to a
  .varlink file in interface_dir, not a Python class
- VarlinkError subclasses for user-defined errors require manually
  constructing the {"error": ..., "parameters": {...}} dict
- RequestHandler requires a `service` class variable wired to the
  Service instance
- ThreadingServer and ForkingServer live in the varlink package, not
  socketserver; add docstrings and list all server classes in the
  module docstring
@haraldh haraldh merged commit 3292a46 into master Feb 16, 2026
8 of 9 checks passed
@haraldh haraldh deleted the hh/doc-improvement branch February 16, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants